//
// Created by xzl on 2018/2/27.
//

#include "MvManager.h"
#include "Util/logger.h"
#include "Util/util.h"
#include "Util/TimeTicker.h"
namespace Ftp {
#define FTP_FIELD "ftp."
    //ftp下载链接前缀
    const char kUrlPrefix[] = FTP_FIELD"url_prefix";
    //远程数据库所在ftp路径
    const char kRemoteDbRelativeUrl[] = FTP_FIELD"remote_db_relative_url";
    //ftp用户名
    const char kUser[] = FTP_FIELD"user";
    //ftp密码
    const char kPasswd[] = FTP_FIELD"passwd";
    //远程数据库文件
    const char kDbFileRemote[] = FTP_FIELD"db_file_remote";
    //本地数据库文件
    const char kDbFileLocal[] = FTP_FIELD"db_file_local";
    //数据库检查更新频率
    const char kDbCheckInterval[] = FTP_FIELD"db_check_interval";
    //ftp连接池个数
    const char kDownloaderSize[] = FTP_FIELD"downloader_size";
    //下载任务检查更新频率
    const char kTaskCheckInterval[] = FTP_FIELD"task_check_interval";

    //硬盘列表
    const char kDiskRange[] = FTP_FIELD"disk_range";
    //硬盘名列表
    const char kDiskPrefix[] = FTP_FIELD"disk_prefix";
    //mv保存路径
    const char kMvPath[] = FTP_FIELD"mv_path";

    onceToken token([]() {
        mINI::Instance()[kUrlPrefix] = "ftp://120.237.110.154";
        mINI::Instance()[kRemoteDbRelativeUrl] = "/db/vod.db";
        mINI::Instance()[kUser] = "ktv";
        mINI::Instance()[kPasswd] = "12345";
        mINI::Instance()[kDbFileRemote] = exeDir() + "vod.db";
        mINI::Instance()[kDbFileLocal] = exeDir() + "down_info.db";
        mINI::Instance()[kMvPath] = exeDir() + "mv";
        //每隔60秒检查数据库更新
        mINI::Instance()[kDbCheckInterval] = 5;
        //连接池个数等于cpu个数 * 4
        mINI::Instance()[kDownloaderSize] = MAX(4, MIN(4 * thread::hardware_concurrency(), 16));
        //下载器检查更新频率
        mINI::Instance()[kTaskCheckInterval] = 5;
        //ftp服务器上硬盘可能挂载的文件夹名称
        mINI::Instance()[kDiskRange] = "1-16";
        mINI::Instance()[kDiskPrefix] = "disk";
    }, nullptr);

}//namespace Ftp



MvManager::MvManager() : _threadpool(1,ThreadPool::PRIORITY_HIGHEST){
    GET_CONFIG_AND_REGISTER(int, ftp_ftp_pool_size, Ftp::kDownloaderSize);
    GET_CONFIG_AND_REGISTER(string, ftp_disk_range, Ftp::kDiskRange);
    GET_CONFIG_AND_REGISTER(string, ftp_disk_prefix, Ftp::kDiskPrefix);

    //锁定id
    _lock_id = time(NULL);

    auto vec_disk_range = split(ftp_disk_range, "-");
    if (vec_disk_range.size() != 2) {
        _min_disk = 1;
        _max_disk = 16;
        WarnL << "非法的配置:" << Ftp::kDiskRange;
    } else {
        _min_disk = atoi(vec_disk_range[0].data());
        _max_disk = atoi(vec_disk_range[1].data());
    }
    //获取远程ftp服务器硬盘列表
    if (ftp_disk_prefix.empty()) {
        ftp_disk_prefix = "disk";
        WarnL << "非法的配置:" << Ftp::kDiskPrefix;
    }
    _disk_prefix = ftp_disk_prefix;
    _success_disk = _min_disk;
    _failed_disk = _max_disk + 1;

    _threadpool.async_first([&](){
        _threadid = this_thread::get_id();
        //立即同步一次
        onManager(true);
    });

    //开始定时器
    _timer.reset(new Timer(3, [this](){
        _threadpool.async([this](){
            onManager(false);
        });
        return true;
    }));
}

MvManager::~MvManager() {
    stop();
}
void MvManager::stop(){
    _downloaderMap.clear();
    _timer.reset();
    _localDb.reset();
    _remoteDb.reset();
    _threadpool.wait();
}

FtpUpdateor::Ptr MvManager::getDownloader(const string &url) {
    TimeTicker();
    CHECK_THREAD();
    GET_CONFIG_AND_REGISTER(string, ftp_user, Ftp::kUser);
    GET_CONFIG_AND_REGISTER(string, ftp_pwd, Ftp::kPasswd);
    auto ret = std::make_shared<FtpUpdateor>(ftp_user, ftp_pwd);
    auto it = _downloaderMap.find(url);
    if (it != _downloaderMap.end()) {
        WarnL << "启动相同的下载任务:" << url;
    }else{
        //DebugL << url;
    }
    _downloaderMap[url] = ret;
    return ret;
}
void MvManager::cancelDownloader(const string &url){
    TimeTicker();
    CHECK_THREAD();
    if(!_downloaderMap.erase(url)){
        WarnL << "下载任务不存在:" << url;
    } else{
        //DebugL << url;
    }
}

string MvManager::getAbsolutelyUrl(const string &relative_url, const string &middle_dir) {
    GET_CONFIG_AND_REGISTER(string, ftp_prefix_url, Ftp::kUrlPrefix);
    if (middle_dir.empty()) {
        return ftp_prefix_url + relative_url;
    }
    return ftp_prefix_url + "/" + middle_dir + "/" + relative_url;
}

string MvManager::getSavedPath(const string &filename) {
    GET_CONFIG_AND_REGISTER(string, mv_save_prefix, Ftp::kMvPath);
    return mv_save_prefix + "/" + filename;
}

void MvManager::checkUpdateRemoteDB() {
    TimeTicker();
    CHECK_THREAD();
    GET_CONFIG_AND_REGISTER(string, ftp_remote_db_relative_url, Ftp::kRemoteDbRelativeUrl);
    GET_CONFIG_AND_REGISTER(string, ftp_db_file_remote, Ftp::kDbFileRemote);
    auto db_url = getAbsolutelyUrl(ftp_remote_db_relative_url);
    _updatingDB = true;

    getDownloader(db_url)->update(db_url, ftp_db_file_remote,[this,db_url](int code, const string &msg) {
        _threadpool.async([code, msg, this, db_url]() {
            onUpdateDBResult(code, msg);
            cancelDownloader(db_url);
        });
    });
}

void MvManager::onUpdateDBResult(int code, const string &msg) {
    TimeTicker();
    CHECK_THREAD();
    _tickerCheckDB.resetTime();
    _updatingDB = false;
    if (code == 0) {
        //更新数据库成功,说明磁盘名可能也变更了
        _failed_disk = _max_disk + 1;
        _remoteDb.reset();
        DebugL << "更新数据库成功，失败磁盘索引设定为:" << _failed_disk;
    }
}

bool MvManager::onManager(bool now) {
    TimeTicker();
    CHECK_THREAD();
    try {
        GET_CONFIG_AND_REGISTER(int, db_check_interval, Ftp::kDbCheckInterval);
        if ((_tickerCheckDB.elapsedTime() > db_check_interval * 1000 || now) && !_updatingDB) {
            //每隔一段时间检查数据库更新
            _tickerCheckDB.resetTime();
            checkUpdateRemoteDB();
        }

        GET_CONFIG_AND_REGISTER(int, task_check_interval, Ftp::kTaskCheckInterval);
        if ((_tickerCheckTasks.elapsedTime() > task_check_interval * 1000 || now)) {
            //每隔一段时间检查文件下载任务
            _tickerCheckTasks.resetTime();
            startAllTasks();
        }

        int speed = 0;
        for(auto &pr : _downloaderMap){
            speed += pr.second->downloadSpeed();
        }
        DebugL << "下载总速度:" << speed / 1024 << " KB/s";
    } catch (std::exception &ex) {
        WarnL << ex.what();
    }
    return true;
}

void MvManager::startAllTasks() {
    TimeTicker();
    CHECK_THREAD();
    GET_CONFIG_AND_REGISTER(int, downloader_size, Ftp::kDownloaderSize);
    if (_downloaderMap.size() >= downloader_size) {
        //已经有很多下载器了，不能再添加了
        return;
    }
    //本次可能启动的下载任务
    int maybe_size = downloader_size - _downloaderMap.size();
    std::vector<MvInfoMoudle> task_list;
    //恢复下载下载中的文件
    queryLocalDBWithResult(task_list, get_all<MvInfoMoudle>(where(c(&MvInfoMoudle::completed) != 1
                                                                  and c(&MvInfoMoudle::lock_id) != _lock_id
                                                                  and c(&MvInfoMoudle::failed_code) !=
                                                                      (int) CURLE_FTP_COULDNT_RETR_FILE),
                                                            order_by(&MvInfoMoudle::songbm).desc(),
                                                            limit(maybe_size)));
    for (auto task : task_list) {
        resumTask(task);
    }
    if (maybe_size > task_list.size()) {
        //启动的下载任务少于实际个数，那么就新建任务
        createTasks(maybe_size - task_list.size());
    }
}

void MvManager::resumTask(const MvInfoMoudle &moudle) {
    TimeTicker();
    CHECK_THREAD();
    {
        //被本次进程锁定，不会重新下载该文件
        auto &moudle_rw = const_cast<MvInfoMoudle &>(moudle);
        moudle_rw.lock_id = _lock_id;
        queryLocalDB(update<MvInfoMoudle>(moudle_rw));
    }
    if (moudle.down_url.empty()) {
        //没有获取下载路径，重新获取
        createTask(moudle, true);
        return;
    }

    InfoL << "开始下载：" << moudle.down_url;
    getDownloader(moudle.down_url)->download(moudle.down_url, moudle.file_path, true, [moudle, this](int code, const string &errMsg) {
                             auto &moudle_rw = const_cast<MvInfoMoudle &>(moudle);
                             if (code == 0) {
                                 //success
                                 moudle_rw.completed = 1;
                                 InfoL << "下载：" << moudle.down_url << "成功";
                             } else {
                                 //failed
                                 moudle_rw.completed = 0;
                                 ++moudle_rw.failed_count;
                                 moudle_rw.failed_code = code;
                                 moudle_rw.failed_msg = errMsg;
                                 WarnL << "下载：" << moudle.down_url << "失败:" << errMsg;
                             }
                             moudle_rw.update_time = time(NULL);
                             //解锁该文件
                             moudle_rw.lock_id = 0;
                             _threadpool.async([moudle_rw, this]() {
                                 queryLocalDB(update<MvInfoMoudle>(moudle_rw));
                                 cancelDownloader(moudle_rw.down_url);
                             });
                         });
}


void MvManager::createTasks(int size) {
    TimeTicker();
    CHECK_THREAD();
    //获取已下载mv中最大歌曲编码
    auto maxSongBM = getMaxLocalSongBM();

    try {
        //获取最大编码后面的若干歌曲
        auto task_list = getRemoteStorage().get_all<MvInfoMoudle>(where(c(&MvInfoMoudle::songbm) > maxSongBM),
                                                                  order_by(&MvInfoMoudle::songbm).asc(), limit(size));
        for (auto task : task_list) {
            createTask(task, false);
        }
    } catch (std::exception &ex) {
        _remoteDb.reset();
        WarnL << ex.what();
    }
}

string MvManager::getMaxLocalSongBM() {
    TimeTicker();
    CHECK_THREAD();
    shared_ptr<string> ret;
    queryLocalDBWithResult(ret, max(&MvInfoMoudle::songbm));
    if (!ret) {
        return "0";
    }
    return *ret;
}

void MvManager::createTask(const MvInfoMoudle &moudle, bool update) {
    TimeTicker();
    CHECK_THREAD();
    if (!update) {
        //先把数据写入数据库，占个茅坑，防止生产相同的记录
        MvInfoMoudle &moudle_rw = const_cast<MvInfoMoudle &>(moudle);
        moudle_rw.down_url = "";//尚未获取到下载路径，正在获取中
        moudle_rw.file_path = getSavedPath(moudle_rw.filename);//保存路径
        moudle_rw.update_time = time(NULL);//时间
        moudle_rw.create_time = moudle_rw.update_time;//时间
        moudle_rw.completed = 0;//未下载完毕
        moudle_rw.failed_count = 0;//失败次数
        moudle_rw.failed_code = 0;//失败代码
        moudle_rw.failed_msg = "";//失败提示
        moudle_rw.lock_id = _lock_id;//锁定该记录
        queryLocalDBWithResult(moudle_rw.row_id, insert<MvInfoMoudle>(moudle_rw));
    }

    startGuessFullUrl(moudle.filename, [moudle, this, update](const string &full_url, int code, const string &msg) {
        //获取完整ftp url
        InfoL << "获取文件路径结果:" << full_url << " " << code << " " << msg;
        MvInfoMoudle &moudle_rw = const_cast<MvInfoMoudle &>(moudle);
        moudle_rw.down_url = full_url;
        moudle_rw.update_time = time(NULL);
        moudle_rw.failed_count += (code == 0 ? 0 : 1);
        moudle_rw.failed_code = code;
        moudle_rw.failed_msg = msg;
        moudle_rw.lock_id = 0;//解锁该记录
        queryLocalDB(update < MvInfoMoudle > (moudle_rw));
    });
}

void MvManager::startGuessFullUrl(const string &fileName, const onGuessUrl &callback) {
    TimeTicker();
    CHECK_THREAD();
    //从_success_disk到_failed_disk-1间尝试获取完整的url路径
    guessFullUrl(fileName, _success_disk, _success_disk, _failed_disk - 1, callback);
}

void MvManager::guessFullUrl(const string &fileName,
                             int disk_index,
                             int start_index,
                             int end_index,
                             const onGuessUrl &callback) {
    TimeTicker();
    CHECK_THREAD();
    auto fullUrl = getAbsolutelyUrl(fileName, _disk_prefix + to_string(disk_index));
    getDownloader(fullUrl)->getRemoteFileInfo(fullUrl, [=] (int fileSize, int fileTime, int code, const string &errMsg) {
        //成功，说明该文件存在
            _threadpool.async([=]() {
            cancelDownloader(fullUrl);
            switch (code) {
                case 0: {
                    //成功，更新最近定位成功的磁盘号
                    if (_success_disk != disk_index) {
                        _success_disk = disk_index;
                        DebugL << "成功磁盘索引设定为:" << _success_disk << " " << fullUrl;
                    }
                    callback(fullUrl, code, errMsg);
                }
                    break;

                case CURLE_REMOTE_ACCESS_DENIED :
                    //没有该目录，更新最近失败的磁盘号，防止下次还尝试该磁盘
                    if (_failed_disk != disk_index) {
                        _failed_disk = disk_index;
                        DebugL << "失败磁盘索引设定为:" << _failed_disk;
                    }
                    (int &) end_index = disk_index;
                case CURLE_FTP_COULDNT_RETR_FILE: {
                    //没有文件或没有目录
                    if (disk_index == end_index) {
                        //最后的硬盘都没该文件
                        if (start_index == _min_disk) {
                            //这个是从头开始找的，说明从头到尾都没找到文件
                            callback("", code, errMsg);
                        } else {
                            //这个是从中间磁盘开始找到，我们从_min_disk到start_index-1再找一遍，再不行就说明真的不在服务器上
                            guessFullUrl(fileName, _min_disk, _min_disk, start_index - 1, callback);
                        }
                    } else {
                        //尝试下一个磁盘
                        guessFullUrl(fileName, disk_index + 1, start_index, end_index, callback);
                    }
                }
                    break;
                default: {//其他错误，可能是由于网络错误,那么我们就不再递归重试了
                    callback("", code, errMsg);
                }
                    break;
            }
        });
    });
}

